package JoReg

import "fmt"

const (
	STMT          = "stmt"
	EXPR          = "expr"
	EXPR_PRIME    = "expr'"
	TERM          = "term"
	TERM_PRIME    = "term'"
	FACTOR        = "factor"
	NUM_OR_ID     = "num_or_id"
	PLUS_         = "plus"
	SEMI_         = "semi"
	MULTIPLE      = "multiple"
	LEFT_PAREN    = "("
	RIGHT_PAREN   = ")"
	LEFT_BRACKET  = "["
	RIGHT_BRACKET = "]"
	LEFT_CURLY    = "{"
	RIGHT_CURLY   = "}"
	ACTION_0      = "getName()"
	ACTION_1      = "freeName()"
	ACTION_2      = "println(+=);freeName()"
	ACTION_3      = "println(*=);freeName()"
	ACTION_4      = "println(=)"
)

type PdaStmt struct {
	lexerPtr     *Lexer
	assignPtr    *Assign
	nameStackPtr *Stack
	pdaStackPtr  *Stack
}

func MakePdaStmt(express string) *PdaStmt {
	lexerPtr := MakeLexer()
	lexerPtr.ParseToken(express)
	assignPtr := MakeAssign()
	nameStackPtr := MakeStack()
	pdaStackPtr := MakeStack()
	pdaStmtPtr := &PdaStmt{}
	pdaStmtPtr.lexerPtr = lexerPtr
	pdaStmtPtr.assignPtr = assignPtr
	pdaStmtPtr.nameStackPtr = nameStackPtr
	pdaStmtPtr.pdaStackPtr = pdaStackPtr
	pdaStackPtr.Push(STMT)
	return pdaStmtPtr
}

func (this *PdaStmt) parse() {
	for {
		if this.pdaStackPtr.IsEmpty() {
			break
		}
		grammar := this.pdaStackPtr.Pop()
		switch grammar {
		case STMT:
			if this.lexerPtr.isEos() {
				break
			}
			this.pdaStackPtr.Push(STMT)
			this.pdaStackPtr.Push(SEMI_)
			this.pdaStackPtr.Push(ACTION_1)
			this.pdaStackPtr.Push(EXPR)
			this.pdaStackPtr.Push(ACTION_0)
		case EXPR:
			this.pdaStackPtr.Push(EXPR_PRIME)
			this.pdaStackPtr.Push(TERM)
		case EXPR_PRIME:
			if this.lexerPtr.matchToken(PLUS) {
				this.pdaStackPtr.Push(EXPR_PRIME)
				this.pdaStackPtr.Push(ACTION_2)
				this.pdaStackPtr.Push(TERM)
				this.pdaStackPtr.Push(ACTION_0)
				this.pdaStackPtr.Push(PLUS_)
			}
		case TERM:
			this.pdaStackPtr.Push(TERM_PRIME)
			this.pdaStackPtr.Push(FACTOR)
		case TERM_PRIME:
			if this.lexerPtr.matchToken(ASTERISK) {
				this.pdaStackPtr.Push(TERM_PRIME)
				this.pdaStackPtr.Push(ACTION_3)
				this.pdaStackPtr.Push(FACTOR)
				this.pdaStackPtr.Push(ACTION_0)
				this.pdaStackPtr.Push(MULTIPLE)
			}
		case FACTOR:
			if this.lexerPtr.matchToken(NUMBER) {
				this.pdaStackPtr.Push(ACTION_4)
				this.pdaStackPtr.Push(NUM_OR_ID)
			} else if this.lexerPtr.matchToken(OPEN_PAREN) {
				this.pdaStackPtr.Push(RIGHT_PAREN)
				this.pdaStackPtr.Push(EXPR)
				this.pdaStackPtr.Push(LEFT_PAREN)
			}
		case NUM_OR_ID:
			if !this.lexerPtr.matchToken(NUMBER) {
				fmt.Printf("number parse error \n")
			}
		case PLUS_:
			if this.lexerPtr.matchToken(PLUS) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("plus parse error \n")
			}
		case SEMI_:
			if this.lexerPtr.matchToken(SEMI) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("semi parse error \n")
			}
		case MULTIPLE:
			if this.lexerPtr.matchToken(ASTERISK) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("multiple parse error \n")
			}
		case LEFT_PAREN:
			if this.lexerPtr.matchToken(OPEN_PAREN) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("open paren parse error \n")
			}
		case RIGHT_PAREN:
			if this.lexerPtr.matchToken(CLOSE_PAREN) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("close paren parse error \n")
			}
		case LEFT_BRACKET:
		case RIGHT_BRACKET:
		case LEFT_CURLY:
		case RIGHT_CURLY:
		case ACTION_0:
			varName := this.assignPtr.getName()
			this.nameStackPtr.Push(varName)
		case ACTION_1:
			this.nameStackPtr.Pop()
			this.assignPtr.freeName()
		case ACTION_2:
			varName := this.nameStackPtr.Pop()
			upName := this.nameStackPtr.Top()
			fmt.Printf("%s += %s\n", upName, varName)
			this.assignPtr.freeName()
		case ACTION_3:
			varName := this.nameStackPtr.Pop()
			upName := this.nameStackPtr.Top()
			fmt.Printf("%s *= %s\n", upName, varName)
			this.assignPtr.freeName()
		case ACTION_4:
			varName := this.nameStackPtr.Top()
			fmt.Printf("%s = %c\n", varName, this.lexerPtr.curToken().lexeme)
			this.lexerPtr.advance()
		}
	}
}
